/*
 * Copyright 2024 ywcai
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

`include "defines.v"
`timescale 1ns/1ps

module mul(
    input   wire[3:0]               mux_mul_ctrl_i,
	input	wire					w_suffix_i,
	input	wire[`RegDataBus]		mul_operand_1_i,
	input	wire[`RegDataBus]		mul_operand_2_i,

	output	wire[127:0]				mul_result_o
	);

    wire i_inst_mul = (mux_mul_ctrl_i == 4'b0001);
    wire i_inst_mulh = (mux_mul_ctrl_i == 4'b0010);
    wire i_inst_mulhu = (mux_mul_ctrl_i == 4'b0011);
    wire i_inst_mulhsu = (mux_mul_ctrl_i == 4'b0100);

    wire[`RegDataBus] op_1_complemented = ~mul_operand_1_i + `XLEN'h1;
    wire[`RegDataBus] op_2_complemented = ~mul_operand_2_i + `XLEN'h1;

	wire[`RegDataBus] op_1 = (i_inst_mul && w_suffix_i) ? {32'h0, mul_operand_1_i[31:0]} : mul_operand_1_i;
	wire[`RegDataBus] op_2 = (i_inst_mul && w_suffix_i) ? {32'h0, mul_operand_2_i[31:0]} : mul_operand_2_i;

	wire mul_sign = i_inst_mul || i_inst_mulh || i_inst_mulhsu;
    wire op_1_neg = mul_sign && op_1[63];
    wire op_2_neg = (i_inst_mul || i_inst_mulh) && op_2[63];

    wire[`RegDataBus] mul_op_1 =
        ({`XLEN{(mul_sign && !op_1_neg) || i_inst_mulhu}} & op_1)
        | ({`XLEN{mul_sign && op_1_neg}} & op_1_complemented);
    wire[`RegDataBus] mul_op_2 =
        ({`XLEN{(mul_sign && !op_2_neg) || i_inst_mulhu}} & op_2)
        | ({`XLEN{mul_sign && op_2_neg}} & op_2_complemented);

    wire[`DXLEN:0] mul_res;
	wire[96:0] res_high;

	wire[65:0] part0  = mul_op_2[0]  ? {2'b0, mul_op_1} : 66'h0;
	wire[65:0] part1  = mul_op_2[1]  ? {1'b0, mul_op_1, 1'b0} : 66'h0;
	wire[65:0] part2  = mul_op_2[2]  ? {2'b0, mul_op_1} : 66'h0;
	wire[65:0] part3  = mul_op_2[3]  ? {1'b0, mul_op_1, 1'b0} : 66'h0;
	wire[65:0] part4  = mul_op_2[4]  ? {2'b0, mul_op_1} : 66'h0;
	wire[65:0] part5  = mul_op_2[5]  ? {1'b0, mul_op_1, 1'b0} : 66'h0;
	wire[65:0] part6  = mul_op_2[6]  ? {2'b0, mul_op_1} : 66'h0;
	wire[65:0] part7  = mul_op_2[7]  ? {1'b0, mul_op_1, 1'b0} : 66'h0;
	wire[65:0] part8  = mul_op_2[8]  ? {2'b0, mul_op_1} : 66'h0;
	wire[65:0] part9  = mul_op_2[9]  ? {1'b0, mul_op_1, 1'b0} : 66'h0;
	wire[65:0] part10 = mul_op_2[10] ? {2'b0, mul_op_1} : 66'h0;
	wire[65:0] part11 = mul_op_2[11] ? {1'b0, mul_op_1, 1'b0} : 66'h0;
	wire[65:0] part12 = mul_op_2[12] ? {2'b0, mul_op_1} : 66'h0;
	wire[65:0] part13 = mul_op_2[13] ? {1'b0, mul_op_1, 1'b0} : 66'h0;
	wire[65:0] part14 = mul_op_2[14] ? {2'b0, mul_op_1} : 66'h0;
	wire[65:0] part15 = mul_op_2[15] ? {1'b0, mul_op_1, 1'b0} : 66'h0;
	wire[65:0] part16 = mul_op_2[16] ? {2'b0, mul_op_1} : 66'h0;
	wire[65:0] part17 = mul_op_2[17] ? {1'b0, mul_op_1, 1'b0} : 66'h0;
	wire[65:0] part18 = mul_op_2[18] ? {2'b0, mul_op_1} : 66'h0;
	wire[65:0] part19 = mul_op_2[19] ? {1'b0, mul_op_1, 1'b0} : 66'h0;
	wire[65:0] part20 = mul_op_2[20] ? {2'b0, mul_op_1} : 66'h0;
	wire[65:0] part21 = mul_op_2[21] ? {1'b0, mul_op_1, 1'b0} : 66'h0;
	wire[65:0] part22 = mul_op_2[22] ? {2'b0, mul_op_1} : 66'h0;
	wire[65:0] part23 = mul_op_2[23] ? {1'b0, mul_op_1, 1'b0} : 66'h0;
	wire[65:0] part24 = mul_op_2[24] ? {2'b0, mul_op_1} : 66'h0;
	wire[65:0] part25 = mul_op_2[25] ? {1'b0, mul_op_1, 1'b0} : 66'h0;
	wire[65:0] part26 = mul_op_2[26] ? {2'b0, mul_op_1} : 66'h0;
	wire[65:0] part27 = mul_op_2[27] ? {1'b0, mul_op_1, 1'b0} : 66'h0;
	wire[65:0] part28 = mul_op_2[28] ? {2'b0, mul_op_1} : 66'h0;
	wire[65:0] part29 = mul_op_2[29] ? {1'b0, mul_op_1, 1'b0} : 66'h0;
	wire[65:0] part30 = mul_op_2[30] ? {2'b0, mul_op_1} : 66'h0;
	wire[65:0] part31 = mul_op_2[31] ? {1'b0, mul_op_1, 1'b0} : 66'h0;
	wire[65:0] part32 = mul_op_2[32] ? {2'b0, mul_op_1} : 66'h0;
	wire[65:0] part33 = mul_op_2[33] ? {1'b0, mul_op_1, 1'b0} : 66'h0;
	wire[65:0] part34 = mul_op_2[34] ? {2'b0, mul_op_1} : 66'h0;
	wire[65:0] part35 = mul_op_2[35] ? {1'b0, mul_op_1, 1'b0} : 66'h0;
	wire[65:0] part36 = mul_op_2[36] ? {2'b0, mul_op_1} : 66'h0;
	wire[65:0] part37 = mul_op_2[37] ? {1'b0, mul_op_1, 1'b0} : 66'h0;
	wire[65:0] part38 = mul_op_2[38] ? {2'b0, mul_op_1} : 66'h0;
	wire[65:0] part39 = mul_op_2[39] ? {1'b0, mul_op_1, 1'b0} : 66'h0;
	wire[65:0] part40 = mul_op_2[40] ? {2'b0, mul_op_1} : 66'h0;
	wire[65:0] part41 = mul_op_2[41] ? {1'b0, mul_op_1, 1'b0} : 66'h0;
	wire[65:0] part42 = mul_op_2[42] ? {2'b0, mul_op_1} : 66'h0;
	wire[65:0] part43 = mul_op_2[43] ? {1'b0, mul_op_1, 1'b0} : 66'h0;
	wire[65:0] part44 = mul_op_2[44] ? {2'b0, mul_op_1} : 66'h0;
	wire[65:0] part45 = mul_op_2[45] ? {1'b0, mul_op_1, 1'b0} : 66'h0;
	wire[65:0] part46 = mul_op_2[46] ? {2'b0, mul_op_1} : 66'h0;
	wire[65:0] part47 = mul_op_2[47] ? {1'b0, mul_op_1, 1'b0} : 66'h0;
	wire[65:0] part48 = mul_op_2[48] ? {2'b0, mul_op_1} : 66'h0;
	wire[65:0] part49 = mul_op_2[49] ? {1'b0, mul_op_1, 1'b0} : 66'h0;
	wire[65:0] part50 = mul_op_2[50] ? {2'b0, mul_op_1} : 66'h0;
	wire[65:0] part51 = mul_op_2[51] ? {1'b0, mul_op_1, 1'b0} : 66'h0;
	wire[65:0] part52 = mul_op_2[52] ? {2'b0, mul_op_1} : 66'h0;
	wire[65:0] part53 = mul_op_2[53] ? {1'b0, mul_op_1, 1'b0} : 66'h0;
	wire[65:0] part54 = mul_op_2[54] ? {2'b0, mul_op_1} : 66'h0;
	wire[65:0] part55 = mul_op_2[55] ? {1'b0, mul_op_1, 1'b0} : 66'h0;
	wire[65:0] part56 = mul_op_2[56] ? {2'b0, mul_op_1} : 66'h0;
	wire[65:0] part57 = mul_op_2[57] ? {1'b0, mul_op_1, 1'b0} : 66'h0;
	wire[65:0] part58 = mul_op_2[58] ? {2'b0, mul_op_1} : 66'h0;
	wire[65:0] part59 = mul_op_2[59] ? {1'b0, mul_op_1, 1'b0} : 66'h0;
	wire[65:0] part60 = mul_op_2[60] ? {2'b0, mul_op_1} : 66'h0;
	wire[65:0] part61 = mul_op_2[61] ? {1'b0, mul_op_1, 1'b0} : 66'h0;
	wire[65:0] part62 = mul_op_2[62] ? {2'b0, mul_op_1} : 66'h0;
	wire[65:0] part63 = mul_op_2[63] ? {1'b0, mul_op_1, 1'b0} : 66'h0;

	// first bit is carry-bit
	wire[65:0] tier1_0 = part1 + part0;
	assign mul_res[1:0] = tier1_0[1:0];
	wire[65:0] tier1_1 = part3 + part2;
	wire[65:0] tier1_2 = part5 + part4;
	wire[65:0] tier1_3 = part7 + part6;
	wire[65:0] tier1_4 = part9 + part8;
	wire[65:0] tier1_5 = part11 + part10;
	wire[65:0] tier1_6 = part13 + part12;
	wire[65:0] tier1_7 = part15 + part14;
	wire[65:0] tier1_8 = part17 + part16;
	wire[65:0] tier1_9 = part19 + part18;
	wire[65:0] tier1_10 = part21 + part20;
	wire[65:0] tier1_11 = part23 + part22;
	wire[65:0] tier1_12 = part25 + part24;
	wire[65:0] tier1_13 = part27 + part26;
	wire[65:0] tier1_14 = part29 + part28;
	wire[65:0] tier1_15 = part31 + part30;
	wire[65:0] tier1_16 = part33 + part32;
	wire[65:0] tier1_17 = part35 + part34;
	wire[65:0] tier1_18 = part37 + part36;
	wire[65:0] tier1_19 = part39 + part38;
	wire[65:0] tier1_20 = part41 + part40;
	wire[65:0] tier1_21 = part43 + part42;
	wire[65:0] tier1_22 = part45 + part44;
	wire[65:0] tier1_23 = part47 + part46;
	wire[65:0] tier1_24 = part49 + part48;
	wire[65:0] tier1_25 = part51 + part50;
	wire[65:0] tier1_26 = part53 + part52;
	wire[65:0] tier1_27 = part55 + part54;
	wire[65:0] tier1_28 = part57 + part56;
	wire[65:0] tier1_29 = part59 + part58;
	wire[65:0] tier1_30 = part61 + part60;
	wire[65:0] tier1_31 = part63 + part62;

	// first bit is carry-bit
	wire[67:0] tier2_0 = {tier1_1, 2'b0} + {2'b0, tier1_0};
	assign mul_res[3:2] = tier2_0[3:2];
	wire[67:0] tier2_1 = {tier1_3, 2'b0} + {2'b0, tier1_2};
	wire[67:0] tier2_2 = {tier1_5, 2'b0} + {2'b0, tier1_4};
	wire[67:0] tier2_3 = {tier1_7, 2'b0} + {2'b0, tier1_6};
	wire[67:0] tier2_4 = {tier1_9, 2'b0} + {2'b0, tier1_8};
	wire[67:0] tier2_5 = {tier1_11, 2'b0} + {2'b0, tier1_10};
	wire[67:0] tier2_6 = {tier1_13, 2'b0} + {2'b0, tier1_12};
	wire[67:0] tier2_7 = {tier1_15, 2'b0} + {2'b0, tier1_14};
	wire[67:0] tier2_8 = {tier1_17, 2'b0} + {2'b0, tier1_16};
	wire[67:0] tier2_9 = {tier1_19, 2'b0} + {2'b0, tier1_18};
	wire[67:0] tier2_10 = {tier1_21, 2'b0} + {2'b0, tier1_20};
	wire[67:0] tier2_11 = {tier1_23, 2'b0} + {2'b0, tier1_22};
	wire[67:0] tier2_12 = {tier1_25, 2'b0} + {2'b0, tier1_24};
	wire[67:0] tier2_13 = {tier1_27, 2'b0} + {2'b0, tier1_26};
	wire[67:0] tier2_14 = {tier1_29, 2'b0} + {2'b0, tier1_28};
	wire[67:0] tier2_15 = {tier1_31, 2'b0} + {2'b0, tier1_30};

	wire[71:0] tier3_0 = {tier2_1, 4'h0} + {4'h0, tier2_0};
	assign mul_res[7:4] = tier3_0[7:4];
	wire[71:0] tier3_1 = {tier2_3, 4'h0} + {4'h0, tier2_2};
	wire[71:0] tier3_2 = {tier2_5, 4'h0} + {4'h0, tier2_4};
	wire[71:0] tier3_3 = {tier2_7, 4'h0} + {4'h0, tier2_6};
	wire[71:0] tier3_4 = {tier2_9, 4'h0} + {4'h0, tier2_8};
	wire[71:0] tier3_5 = {tier2_11, 4'h0} + {4'h0, tier2_10};
	wire[71:0] tier3_6 = {tier2_13, 4'h0} + {4'h0, tier2_12};
	wire[71:0] tier3_7 = {tier2_15, 4'h0} + {4'h0, tier2_14};

	wire[79:0] tier4_0 = {tier3_1, 8'h0} + {8'h0, tier3_0};
	assign mul_res[15:8] = tier4_0[15:8];
	wire[79:0] tier4_1 = {tier3_3, 8'h0} + {8'h0, tier3_2};
	wire[79:0] tier4_2 = {tier3_5, 8'h0} + {8'h0, tier3_4};
	wire[79:0] tier4_3 = {tier3_7, 8'h0} + {8'h0, tier3_6};

	wire[95:0] tier5_0 = {tier4_1, 16'h0} + {16'b0, tier4_0};
	assign mul_res[31:16] = tier5_0[31:16];
	wire[95:0] tier5_1 = {tier4_3, 16'b0} + {16'b0, tier4_2};
	assign res_high[96:0] = {{1'b0, tier5_1} + {33'h0, tier5_0[95:32]}};

	assign mul_res[128:32] = res_high[96:0];

	wire[31:0] res_mulw = mul_res[31:0];

    wire result_neg = (((i_inst_mul && !w_suffix_i) || i_inst_mulh)
			&& (mul_operand_1_i[63] ^ mul_operand_2_i[63]))
		|| (i_inst_mulhsu && mul_operand_1_i[63]);

    assign mul_result_o = ({`DXLEN{result_neg}} & (~mul_res[`DXLEN-1:0] + `DXLEN'h1))
        | ({`DXLEN{!result_neg && !w_suffix_i}} & mul_res[`DXLEN-1:0])
		| ({`DXLEN{!result_neg && w_suffix_i}} & {`XLEN'h0, {32{res_mulw[31]}}, res_mulw});

endmodule	
